{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"../../img/ods_stickers.jpg\"/> Обнаружение мошенничества с кредитными картами</center>\n",
    "<center>Индивидуальный проект. Автор: Александр Евгеньевич Ширкин. Slack: @panchos39</center>\n",
    "<center>https://www.kaggle.com/mlg-ulb/creditcardfraud</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Обнаружение мошенничества с кредитными картами\n",
    "Индивидуальный проект. Автор: Александр Евгеньевич Ширкин. Slack: @panchos39\n",
    "https://www.kaggle.com/mlg-ulb/creditcardfraud"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Описание набора данных и признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данном проекте решается задача поиска мошенничества в множестве транзакций с кредитных карт. В наше время, когда большинство деловых сделок совершается электронным способом, все банки заинтересованы в безопасности счетов своих клиентов, и быстрая реакция на мошеннические сделки позволит улучшить качество услуг предоставляемых банком и престижность банка для вкладчиков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Датасет содерижит транзакции сделанные европейцами с кредитных карт с сентября 2013-го года.  В датасете присутствуют такие транзакции, которые проходили в течении двух дней, и из них 492 мошеннические и 284,807 легальные. Данный набор данных очень несбалансирован, целевой класс (мошенничество) составляет всего лишь 0.172% от доли всех транзакций."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данных содержатся только вещественные признаки, которые являются результатом PCA преобразования. К сожалению, из за проблем с конфедициальностью, в данных нет оригинальных признаков и другой косвенной информации о данных. Единственные признаки, которые не были преобразованы, это 'Time' и 'Amount'.\n",
    "\n",
    "<b>Признаки V1, V2, ..., V28 - главные компоненты, полученные после преобразования PCA исходных признаков.</b> \n",
    "\n",
    " \n",
    "\n",
    "<b>Признак 'Time' содержит время в секундах между каждой транзакцией и самой первой транзакцией в наборе данных.</b>\n",
    "\n",
    "<b>Признак 'Amount' - это объем транзакции. Данный признак может использоваться для обучения с учетом издержек классификации.</b>\n",
    "\n",
    "<b>Признак 'Class' - целевая переменная, она принимает значение 1 - если транзакция имеет мошеннический характер, и 0 - если транзакция легальная</b>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Первичный анализ данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Подключаем библиотеки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt # to plot graph\n",
    "import seaborn as sns\n",
    "import time\n",
    "from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold, validation_curve\n",
    "from sklearn.preprocessing import StandardScaler, OneHotEncoder\n",
    "from sklearn.metrics import roc_auc_score, recall_score, f1_score, precision_recall_curve\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "sns.set_style(\"darkgrid\")\n",
    "from imblearn.under_sampling import RandomUnderSampler\n",
    "from imblearn.over_sampling import RandomOverSampler\n",
    "from scipy.sparse import hstack, vstack\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Считываем наш набор данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "PATH_TO_DATA = '../../data/creditcard/'\n",
    "RANDOM_STATE = 17"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.read_csv(os.path.join(PATH_TO_DATA, 'creditcard.csv.zip'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Теперь исследуем набор данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. Мы можем увидеть, что структурно данные состоят из 284,807 строк и 31 столбца.\n",
    "2. Все признаки вещественные, кроме целевой целочисленной переменной\n",
    "3. В данных нет пропусков.\n",
    "4. Время также представленно в виде вещественного числа и представляет время в секундах с начала первой транзакции"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Признак 'Time' не так хорошо интерпретируется, когда время дано в секундах. Давайте предположим, что транзакции начинаются с 00:00 и заканчиваются в 23:59. Таким образом переведем секунды в часы дня"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['hour'] = data['Time'].apply(lambda x: np.ceil(float(x)/3600) % 24)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Т.к. данные собраны с европейского банка, то будет логичным судить, что часовые пояса в Европейских странах не сильно смещены относительно друг друга, и должно оправдаться предположение, что с 1 часа ночи до 5 утра должно наблюдаться сокращение числа транзакций(ночное время) и примерно с 6 часа снова начинается повышение числа транзакций. Проверем наше предположение при помощи сводной таблицы"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.pivot_table(values='Amount',index='Class',columns='hour',aggfunc='count')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как мы можем увидеть, наше предположение оправдалось. С достаточно большой долей уверенности мы можем судить о том, что данные действительно начинаются с 00:00, и мы можем оправданно создать признак, время в часах. В следующем разделе мы визуально подтвердим наше предположение"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['Class'].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Мы видим явный  сильный дисбаланс в распределении целевой переменной\n",
    "\n",
    "- Посмотрим на распределение целевой переменной в процентном соотношении"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def transaction_dist(labels) :\n",
    "    # now let us check in the number of Percentage\n",
    "    Count_Normal_transacation = len(labels[labels ==0 ])\n",
    "    Count_Fraud_transacation = len(labels[labels == 1])\n",
    "    Percentage_of_Normal_transacation = Count_Normal_transacation/(Count_Normal_transacation+Count_Fraud_transacation)\n",
    "    print(\"Процентное соотношение легальных транзакций\",Percentage_of_Normal_transacation*100)\n",
    "    Percentage_of_Fraud_transacation= Count_Fraud_transacation/(Count_Normal_transacation+Count_Fraud_transacation)\n",
    "    print(\"Процентное соотношнеие мошеннических транзакций\",Percentage_of_Fraud_transacation*100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "transaction_dist(data[\"Class\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Теперь мы убедились, что, действтительно, доля мошеннических транзакций составляет всего лишь 0.172% от всех транзакций, что очень мало. \n",
    "- Это говорит о сильном дисбалансе в распределении целевой переменной\n",
    "- Для качественного обучения необходимо использовать методы балансирования выборки , либо взвешивание в модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим как признаки коррелируют друг с другом. Построим матрицу корреляций Пока исключим целевую переменную"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.drop('Class', axis=1).corr()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Признаков достаточно много, и тяжело судить о взаимодействии признаков без визуализации. Но в данный момент мы можем увидеть следующее, \n",
    "- признаки V1, V2, ..., V28 абсолютно не коррелируют друг с другом\n",
    "- Это частично подтверждает тот факт, что признаки являются главными компонентами PCA преобразования, т.к. главные компоненты PCA преобразования являются линейно независимыми векторами, и каждый вектор описывает свою долю дисперсии в данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим как признаки коррелируют с целевой переменной. Исключим признак 'Time', т.к. мы его рассмотрим отдельно"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.drop(['Class'], axis=1).corrwith(data['Class']).sort_values(axis=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Можем увидеть , что есть признаки, которые достаточно сильно трицательно коррелируют, и признаки, которые положительно коррелируют с целевой переменной, но не так сильно. Будем считать корреляцию значимой, если |corr| > 0.1\n",
    "\n",
    "Среди отрицательных корреляций можем выделить признаки( выберем порог, где корреляция меньше 0.1)\n",
    "- V17, V14, V12, V10, V16, V3, V7, V18, V1\n",
    "\n",
    "Cреди положительных корреляций (выберем порог, где корреляция больше 0.1)\n",
    "- V4, V11\n",
    "\n",
    "Разумеется это наши первые предположения о влиянии признаков на целевую переменную и мы пока не делаем общих выводов. \n",
    "\n",
    "Возможно окажется так, что есть комбинации признаков, которые будут коррелировать намного сильнее, чем текущие показатели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Первичный визуальный анализ данных\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построены визуализации (распределения признаков, матрица корреляций и т.д.), описана связь с анализом данным (п. 2). Присутствуют выводы;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на распределение целевой переменной"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(5, 10))\n",
    "sns.countplot(\"Class\",data=data)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Теперь мы можем визуально подтвердить, что в данных наблюдается сильный дисбаланс распределения целевой переменной\n",
    "- Процентное соотношение легальных транзакций 99.82725143693798 %\n",
    "- Процентное соотношнеие мошеннических транзакций 0.1727485630620034 %\n",
    "- Для обучения необходима балансировка выборки"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим, как распределн объем транзакция на легальные и мошеннические транзакции"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Fraud_transacation = data[data[\"Class\"]==1]\n",
    "Normal_transacation= data[data[\"Class\"]==0]\n",
    "plt.figure(figsize=(10,6))\n",
    "plt.subplot(1, 2, 1)\n",
    "Fraud_transacation.Amount.plot.hist(title=\"Мошеннические транзакции\")\n",
    "plt.subplot(1,2,2)\n",
    "Normal_transacation.Amount.plot.hist(title=\"Легальные транзакции\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Распределение легальных транзакций трудно интерпретировать. \n",
    "- Судя по гистограмме, все легальные транзакции проходят объемом меньше 2.5 тысяч у.е\n",
    "- Уточним данные для гистограммы, ограничив объем транзакции 2.5 к. у.е"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Fraud_transacation = data[data[\"Class\"]==1]\n",
    "Normal_transacation= data[data[\"Class\"]==0]\n",
    "plt.figure(figsize=(10,6))\n",
    "plt.subplot(1, 2, 1)\n",
    "Fraud_transacation[Fraud_transacation[\"Amount\"]<= 2500].Amount.plot.hist(title=\"Мошеннические транзакции\")\n",
    "plt.subplot(1, 2, 2)\n",
    "Normal_transacation[Normal_transacation[\"Amount\"]<=2500].Amount.plot.hist(title=\"Легальные транзакции\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вывод :\n",
    "- по объему транзакций на долю легальных и мошеннических транзакций мы не видим каких то явных закономерностей\n",
    "- По объему транзакций нельзя строго судить о характере проведенной операции"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<b>Посмотрим, как распределены легальные и мошеннические транзакции по часам дня</b>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def PlotHistogramHour(df,norm):\n",
    "    bins = np.arange(df['hour'].min(),df['hour'].max()+2)\n",
    "    plt.figure(figsize=(15,4))\n",
    "    sns.distplot(df[df['Class']==0.0]['hour'],\n",
    "                 norm_hist=norm,\n",
    "                 bins=bins,\n",
    "                 kde=False,\n",
    "                 color='b',\n",
    "                 hist_kws={'alpha':.5},\n",
    "                 label='Легальные')\n",
    "    sns.distplot(df[df['Class']==1.0]['hour'],\n",
    "                 norm_hist=norm,\n",
    "                 bins=bins,\n",
    "                 kde=False,\n",
    "                 color='r',\n",
    "                 label='Мошеннические',\n",
    "                 hist_kws={'alpha':.5})\n",
    "    plt.xticks(range(0,24))\n",
    "    plt.legend()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Частотная гистограма Легальных/Мошеннических транзакций на каждый час дня')\n",
    "PlotHistogramHour(data,False)\n",
    "print('*На гистограмме не видно мошеннических транзакций, т.к. их очень мало, необходимо нормализовать гистограммы.\\n')\n",
    "print('Нормализованная гистограма Легальных/Мошеннических транзакций на каждый час дня')\n",
    "\n",
    "PlotHistogramHour(data,True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вывод: \n",
    "- время, когда была совершена транзакция, несомненно вносит определенный вклад в целевую переменную, и есть предпоссылки для того, чтобы ее использовать в модели. \n",
    "- Возможно это будет не сам час, а какой то конкретный интервал суток, например, ночь, что видно по нормализованной гистограмме"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.pairplot(data=pd.concat([data.loc[:,'hour'],data.loc[:,'V1':'V6'],data.loc[:,'Class']],axis=1),\n",
    "             hue='Class',\n",
    "             diag_kind='kde',\n",
    "             plot_kws={'alpha':0.2})\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Инсайты, найденные зависимости"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "- Мы выяснили, время в секундах начинается с полуночи (с 00:00), значит можно справедливо определить время в часах, когда была совершена транзакция\n",
    "- Мы выяснили, что наблюдается повышенная мошенническая активность в ночное время суток"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Выбор метрики"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Для того что определить метрику качества для нашей задачи нужно обратить внимание на следующие факторы:\n",
    "1. Какая решается задача\n",
    "2. Сколько классов\n",
    "3. Присутствует ли дисбаланс в распределении целевого признака\n",
    "4. Какова главная цель решения задачи"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Так как мы решаем задачу бинарной классификации, то имеет смысл рассматривать следующие метрики:\n",
    "    \n",
    "1. Accuracy = TP+TN/Total - доля верных ответов\n",
    "2. Precison = TP/(TP+FP) - Точность\n",
    "3. Recall = TP/(TP+FN) - Полнота\n",
    "4. AUROC - Area Under ROC - Площадь под ROC кривой\n",
    "5. F-score - F-мера (Взвешенное среднее точности и полноты)\n",
    "\n",
    "\n",
    "- TP = True possitive - Истинные срабатываня\n",
    "- TN = True negative - Истинные пропуски\n",
    "- FP = False possitve - Ложные срабатывания\n",
    "- FN= False Negative - Ложные пропуски"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. Так как мы наблюдаем явный дисбаланс в распределении целевой переменной, то доля верных ответов не подходит для оценки качества модели. Мы можем получить достаточно большую долю верных ответов, если просто скажем, что все транзакции легальные\n",
    "2. Точность - более подходящая метрика оценки, но в данной задаче стоит обратить внимание на то, что нам более важно, точно определять мошеннические транзакции, при этом не охватывая их все полностью, либо же мы охватываем все мошеннические транзакции, но некоторые транзакции ошибочно считаем мошенническими, хотя они являются легальными\n",
    "3. Полнота - для интересов банков, наиболее важный параметр в данной задаче является полнота, т.к. не будет серьезных последствий, если мы предупредим клиента о возможности мошеннических операций на его счете, даже если их на самом деле нет, клиент все равно будет доволен, что банк следит за его активами, в обратном же случае, мы упускаем много потенциальных мошеннических транзакций, и клиент может остаться не доволен услугами банка. Поэтому полнота является адекватной метрикой качества модели, но есть еще один подходящий вариант.\n",
    "4. Площадь под ROC кривой - хорошая метрика, если мы стремимся равноценно хорошо увеличивать как Точность, так и Полноту, но в нашем случае, это может привести к очень хорошей точности, но при этом Полнота будет немного меньше, хотя в нашем случае, намного лучше будет, если Полнота будет больше.\n",
    "5. F-мера - можно сказать идеальная метрика для данной задачи, т.к. мы можем указать приоритет, в чем мы больше нуждаемся, в Точности или Полноте, и получить результат, который будет учитывать как Точность, так и Полноту, но при этом больший приоритет будет отдаваться Полноте.\n",
    "\n",
    "Из наших рассуждений следует, что самые подходящие метрики качества для данной задачи:\n",
    "- Полнота\n",
    "- F-мера c приоритетом к Полноте"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Выбор модели\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Мы решаем задачу классификации, соответственно в качестве модели могут подойти следующие:\n",
    "\n",
    "1. Логистическая регрессия\n",
    "2. Случайный лес\n",
    "3. Градиентный бустинг\n",
    "4. Нейронные сети\n",
    "\n",
    "В наше задаче нет особых ограничений на применение различных моделей.\n",
    "* Признаков не много, т.е. методы основанные на деревьях решений подходят\n",
    "* Признаки масштабированы, и не коррелируют друг с другом, также можно применять логистическую регрессию\n",
    "* Нейронные сети также можно применить, но лично по моему мнению, данный тип моделей будет слишком избыточен, т.к. можно построить достаточно адекватную модель с хорошим отбором признаков, следуя принципу Бритвы Окама. Чем проще, тем лучше. В добавок мы получим эффективность на стадии production, если обратим интерес к более элементарным алгоритмам\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Создание новых признаков и описание этого процесса "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Мы создали новый признак - час дня"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['hour'] = data['Time'].apply(lambda x: np.ceil(float(x)/3600) % 24)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Описание предпосыок к этому признаку было рассмотрено выше"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Предобработка данных\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Мы проделаем следующие действия с нашими данными:\n",
    "- Отмасштабируем признак Amount для логистической регрессии\n",
    "- Применеим One Hot Encoding преобразование к признаку hour\n",
    "- Также рассмотрим два подхода работы с несбалансированными выборками: OverSampling и UnderSampling, и посмотрим, что работает лучше\n",
    "- Признак Time уберем из выборки, т.к. он не несет статистической значимости, мы определили более полезный признак, время в часах, что лучше описывает данные\n",
    "- Остальные признаки оставим без изменений, так как они и так являются результатом PCA преобразования"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X, y = data.drop(['Class', 'Time'], axis=1), data['Class']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_size = 0.7\n",
    "n = X.shape[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разделим наши данные на обучающую и тестовую выборку, причем сохраним порядок появляения данных, т.е. в тестовой выборке у нас присутствуют только более поздние данные, чтобы наша модель могла определять мошеннические транзакции на разных временных промежутках"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, y_train = X.iloc[:int(n * train_size)], y.iloc[:int(n * train_size)]\n",
    "X_test, y_test = X.iloc[int(n * train_size):], y.iloc[int(n * train_size):]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "transaction_dist(y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "transaction_dist(y_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ss = StandardScaler()\n",
    "X_train.Amount = pd.Series(ss.fit_transform(X_train.Amount.values.reshape(-1, 1)).flatten(), name=\"Amount\", index=X_train.index)\n",
    "X_test.Amount = pd.Series(ss.transform(X_test.Amount.values.reshape(-1, 1)).flatten(), name=\"Amount\", index=X_test.index)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_test.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Применим One Hot Encoding для признака hour"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ohe = OneHotEncoder(categorical_features='all')\n",
    "time_ohe_train = ohe.fit_transform(X_train['hour'].values.reshape(-1, 1))\n",
    "time_ohe_test = ohe.transform(X_test['hour'].values.reshape(-1, 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_csr = hstack((X_train.drop('hour', axis=1).values, time_ohe_train))\n",
    "X_test_csr = hstack((X_test.drop('hour', axis=1).values, time_ohe_test))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_test_csr.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Реализуем два метода борьбы с несбалансированностью в распределении целевой переменной\n",
    "#### UnderSampling и OverSampling\n",
    "#### И протестируем какой из методов будет лучше работать"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rus = RandomUnderSampler(random_state=17, return_indices=True)\n",
    "ros = RandomOverSampler(random_state=17,  ratio='minority')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_undersampled, y_train_undersampled, idx_train = rus.fit_sample(X_train_csr, y_train)\n",
    "X_train_oversampled, y_train_oversampled = ros.fit_sample(X_train_csr, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "type(X_train_undersampled)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print (\"UnderSampling\")\n",
    "transaction_dist(y_train_undersampled)\n",
    "print (\"--------------------------------------------------------\")\n",
    "print (\"OverSampling\")\n",
    "transaction_dist(y_train_oversampled)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Кросс-валидация и настройка гиперпараметров модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Проведем кросс валидацию для логистической регрессии как для UnderSampling так и OverSampling\n",
    "- будем перебирать различные значения параметра регуляризации C\n",
    "- В качестве метода разбиения, возьмем стратифицированный, т.к. нам необходимо сохранить баланс классов в каждом фолде\n",
    "- в случае с OverSampling поставим количество фолдов равное 5-ти, а в UnderSampling - 3-м, т.к. данныых очень мало, и большое количество фолдов может повлиять на качество обучения\n",
    "- Для воспроизводимости решения, зафиксируем seed равный 17-ти"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lr = LogisticRegression(random_state=17, n_jobs=-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "skf = StratifiedKFold(n_splits=3, random_state=17, shuffle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_params = {'C' : np.linspace(start=0.0001, stop=10, num=50)}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_under = GridSearchCV(estimator=lr, param_grid=grid_params, cv=skf, n_jobs=-1, scoring='recall', verbose=10)\n",
    "grid_over = GridSearchCV(estimator=lr, param_grid=grid_params, cv=skf, n_jobs=-1, scoring='recall', verbose=10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_under.fit(X_train_undersampled, y_train_undersampled)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_over.fit(X_train_oversampled, y_train_oversampled)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_under.best_score_, grid_under.best_params_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_over.best_score_, grid_under.best_params_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Можем увидеть, что намного лучше результат дает undersampling.\n",
    "Здесь есть большой плюс. Размер нашего датасета существенно сокращается, и обучение проходит очень быстро, что дает большой простор для дальнейших экспериментов, и можно перебирать множество параметров"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10 Построение кривых валидации и обучения"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_range = np.linspace(start=0.0001, stop=10, num=50)\n",
    "train_scores, test_scores = validation_curve(\n",
    "    lr, X_train_undersampled, y_train_undersampled, param_name='C', param_range=param_range,\n",
    "    cv=skf, scoring=\"recall\", n_jobs=1)\n",
    "train_scores_mean = np.mean(train_scores, axis=1)\n",
    "train_scores_std = np.std(train_scores, axis=1)\n",
    "test_scores_mean = np.mean(test_scores, axis=1)\n",
    "test_scores_std = np.std(test_scores, axis=1)\n",
    "\n",
    "plt.title(\"Validation Curve with Logistic Regression\")\n",
    "plt.xlabel(\"$\\gamma$\")\n",
    "plt.ylabel(\"Score\")\n",
    "plt.ylim(0.0, 1.1)\n",
    "lw = 2\n",
    "plt.semilogx(param_range, train_scores_mean, label=\"Training score\",\n",
    "             color=\"darkorange\", lw=lw)\n",
    "plt.fill_between(param_range, train_scores_mean - train_scores_std,\n",
    "                 train_scores_mean + train_scores_std, alpha=0.2,\n",
    "                 color=\"darkorange\", lw=lw)\n",
    "plt.semilogx(param_range, test_scores_mean, label=\"Cross-validation score\",\n",
    "             color=\"navy\", lw=lw)\n",
    "plt.fill_between(param_range, test_scores_mean - test_scores_std,\n",
    "                 test_scores_mean + test_scores_std, alpha=0.2,\n",
    "                 color=\"navy\", lw=lw)\n",
    "plt.legend(loc=\"best\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Судя по кривой валидации и обучения можем увидеть слудующее:\n",
    "- кривая обучения и валидации находятся не далеко друг от друга и ошибка невелика, значит мы можем судить, что наша модель не переобучилась, и не недообучилась\n",
    "- кривые не сошлись друг к другу, значит добавление новых данных может улучшить модель"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Прогноз для тестовой или отложенной выборке"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_precision_recall_curve(precision, recall) :\n",
    "    plt.step(recall, precision, color='b', alpha=0.2,\n",
    "         where='post')\n",
    "    plt.fill_between(recall, precision, step='post', alpha=0.2,\n",
    "                 color='b')\n",
    "\n",
    "    plt.xlabel('Recall')\n",
    "    plt.ylabel('Precision')\n",
    "    plt.ylim([0.0, 1.05])\n",
    "    plt.xlim([0.0, 1.0])\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "under_probs = grid_under.predict_proba(X_test_csr)[:, 1]\n",
    "over_probs = grid_over.predict_proba(X_test_csr)[:, 1]\n",
    "\n",
    "under_preds = grid_under.predict(X_test_csr)\n",
    "over_preds = grid_over.predict(X_test_csr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "precision_under, recall_under, _ = precision_recall_curve(y_true=y_test, probas_pred=under_probs, pos_label=1)\n",
    "precision_over, recall_over, _ = precision_recall_curve(y_true=y_test, probas_pred=over_probs, pos_label=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_precision_recall_curve(precision_under, recall_under)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_precision_recall_curve(precision_over, recall_over)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print (\"Under sampling ROC AUC Score\")\n",
    "print (roc_auc_score(y_true=y_test, y_score=under_probs))\n",
    "print (\"Over sampling ROC AUC Score\")\n",
    "print (roc_auc_score(y_true=y_test, y_score=over_probs))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print (\"Under sampling Recall Score\")\n",
    "print (recall_score(y_true=y_test, y_pred=under_preds))\n",
    "print (\"Over sampling Recall Score\")\n",
    "print (recall_score(y_true=y_test, y_pred=over_preds))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результаты сравнимы с результатами на кросс - валидации, вдобавок, мы можем увидеть что модель имеет хорошую обобщающую способность, т.к. дает хороший результат"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. Выводы"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "- Как можно увидеть, мы добились качества модели по метрике recall в 100%\n",
    "по ROC AUC score - 0.979\n",
    "\n",
    "- Это очень хороший результат, так как мы находим все мошеннические транзакции на тестовой выборке, и немного ошибаемся, определяя легальные транзакции как мошеннические.\n",
    "\n",
    "- Но с точки зрения бизнеса и банковской сферы это не так страшно\n",
    "\n",
    "- Стоит отметить, что до добавления признака hour, модель работала существенно хуже, что подтверждает гипотезу, о том, что данный признак вносит большой вклад в целевую переменную, и большинство нелегальных транзакций проходят ночью"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Прошу обратить внимание, что мы использовали только модель логистической регрессии, и добились хорошего результата. В последующем улучшении нашего решения, можно попробовать использовать другие методы, такие как случайный лес и градиентный бустинг, что возможно даст нам прирост в precision, и оставит тот же recall"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
